import { equals } from "./equals";
import { getSingleType } from "./getTypes";
import { isEmpty } from "./isEmpty";
import { isNotEmpty } from "./isNotEmpty";
import { isMap } from "./isMap";
import { isFormData } from "./isFormData";
import { isSet } from "./isSet";
import { isArray } from "./isArray";
import { isPlainObject } from "./isPlainObject";

/**
 * @author lihh
 * @description 比较两个对象
 * @param v1 对象1
 * @param v2 对象2
 */
export function compareBothObject(v1: object, v2: object): boolean {
  // 表示比较完的 cache
  const comparedCache: Array<any> = [];

  try {
    return innerCompareBothObject(v1, v2, comparedCache);
  } finally {
    comparedCache.length = 0;
  }
}

/**
 * @description 缓存中是否存在指定的值
 * @author lihh
 * @param comparedCache 比较完的cache
 * @param v1 比较对象1
 * @param v2 比较对象2
 */
function isExistCache(
  comparedCache: Array<unknown>,
  v1: unknown,
  v2: unknown
): boolean {
  for (let idx = 0; idx < comparedCache.length; idx += 2) {
    if (v1 === comparedCache[idx] && v2 === comparedCache[idx + 1]) {
      return true;
    }
  }
  return false;
}

/**
 * @author lihh
 * @description 判断是否为特殊类型
 * @param v 参数v
 */
function isSpecialValue(v: unknown): boolean {
  return ["error", "function", "promise", "regexp"].includes(getSingleType(v));
}

/**
 * @author lihh
 * @description 是否基本数据类型
 * @param v 判断的字符串
 */
function isBaseType(v: unknown): boolean {
  return [
    "string",
    "number",
    "boolean",
    "null",
    "undefined",
    "bigint",
    "symbol",
    "date",
  ].includes(getSingleType(v));
}

/**
 * @description 判断表单是否相同
 * @author lihh
 * @param f1 form data 1
 * @param f2 form data 2
 * @param comparedCache 比较的cache
 */
function isSameFormData(
  f1: FormData,
  f2: FormData,
  comparedCache: Array<unknown>
): boolean {
  if (isEmpty(f1) && isEmpty(f2)) {
    return true;
  }

  if ((isEmpty(f1) && isNotEmpty(f2)) || (isNotEmpty(f1) && isEmpty(f2))) {
    return false;
  }

  // 来回颠倒比较，因为无法比较长度
  for (const [k1, v1] of f1.entries()) {
    const v2 = f2.get(k1);
    if (!commonJudge(v1, v2, comparedCache)) {
      return false;
    }
  }

  for (const [k2, v2] of f2.entries()) {
    const v1 = f1.get(k2);
    if (!commonJudge(v1, v2, comparedCache)) {
      return false;
    }
  }

  return true;
}

/**
 * @description 判断是否为相同内容的数组
 * @author lihh
 * @param a1 数组 1
 * @param a2 数组 2
 * @param comparedCache 比较的cache 内容
 */
function isSameArrayOrSet(
  a1: Array<unknown>,
  a2: Array<unknown>,
  comparedCache: Array<unknown>
): boolean {
  if (!equals(getSingleType(a1), getSingleType(a2))) {
    return false;
  }

  if (!equals(a1.length, a2.length)) {
    return false;
  }

  for (let idx = 0; idx < a1.length; idx += 1) {
    if (!commonJudge(a1[idx], a2[idx], comparedCache)) {
      return false;
    }
  }

  return true;
}

/**
 * @author lihh
 * @description 判断是否为相同 set
 * @param s1 set value1
 * @param s2 set value2
 * @param comparedCache 比较的cache
 */
function isSameSet<T>(
  s1: Set<T>,
  s2: Set<T>,
  comparedCache: Array<unknown>
): boolean {
  return isSameArrayOrSet([...s1], [...s2], comparedCache);
}

/**
 * @description 是否相同的数组内容
 * @author lihh
 * @param arr1 array 数组1
 * @param arr2 array 数组2
 * @param comparedCache 比较对象
 */
function isSameArray<T>(
  arr1: Array<T>,
  arr2: Array<T>,
  comparedCache: Array<unknown>
) {
  return isSameArrayOrSet(arr1, arr2, comparedCache);
}

/**
 * @author lihh
 * @description 判断相同的对象
 * @param o1 对象1
 * @param o2 对象2
 * @param comparedCache 比较结果的 cache
 */
function isSameObject(o1: object, o2: object, comparedCache: Array<unknown>) {
  if (!equals(getSingleType(o1), getSingleType(o2))) {
    return false;
  }

  const l1 = Object.keys(o1).length,
    l2 = Object.keys(o2).length;
  if (!equals(l1, l2)) {
    return false;
  }

  for (const k1 in o1) {
    if (
      !commonJudge(
        (o1 as Record<string, unknown>)[k1],
        (o2 as Record<string, unknown>)[k1],
        comparedCache
      )
    ) {
      return false;
    }
  }
  return true;
}

/**
 * @author lihh
 * @description 共同的类型判断
 * @param v1 value1 值
 * @param v2 value2 值
 * @param comparedCache 比较cache
 */
function commonJudge(
  v1: unknown,
  v2: unknown,
  comparedCache: Array<unknown>
): boolean {
  // 判断是否为特殊类型
  if (isSpecialValue(v1) || isSpecialValue(v2)) {
    return false;
  }

  // 必须基于类型相同实现
  if (!equals(getSingleType(v1), getSingleType(v2))) {
    return false;
  }

  // 判断是否为基础数据类型
  if (isBaseType(v1)) {
    if (!equals(v1, v2)) {
      return false;
    }
  }

  // 判断缓存值 因为缓存过的值 都会保存起来，避免死循环递归
  if (isExistCache(comparedCache, v1, v2)) {
    return true;
  }

  // 判断是否为纯对象
  if (isPlainObject(v1)) {
    if (!isSameObject(v1, v2 as object, comparedCache)) {
      return false;
    }
  }

  // 判断是否为 map
  if (isMap(v1)) {
    if (
      !isSameMap(
        v1 as Map<string, unknown>,
        v2 as Map<string, unknown>,
        comparedCache
      )
    ) {
      return false;
    }
  }

  // -- 3. 判断是否为FormData
  if (isFormData(v1)) {
    if (!isSameFormData(v1 as FormData, v2 as FormData, comparedCache)) {
      return false;
    }
  }

  // -- 4. 判断是否为Set
  if (isSet(v1)) {
    if (!isSameSet(v1 as Set<unknown>, v2 as Set<undefined>, comparedCache)) {
      return false;
    }
  }

  // -- 5. 判断是否为 数组
  if (isArray(v1)) {
    if (
      !isSameArray(v1 as Array<unknown>, v2 as Array<unknown>, comparedCache)
    ) {
      return false;
    }
  }

  comparedCache.push(v1, v2);
  return true;
}

/**
 * @description 是否相同的 Map
 * @author lihh
 * @param m1 Map对象 1
 * @param m2 Map对象 2
 * @param comparedCache 更新缓存
 */
function isSameMap(
  m1: Map<string, unknown>,
  m2: Map<string, unknown>,
  comparedCache: Array<unknown>
): boolean {
  if ((isEmpty(m1) && isEmpty(m2)) || (isEmpty(m1.size) && isEmpty(m2.size))) {
    return true;
  }
  if (
    (isEmpty(m1) && isNotEmpty(m2)) ||
    (isNotEmpty(m1) && isEmpty(m2)) ||
    !equals(m1.size, m2.size)
  ) {
    return false;
  }

  for (const [k1, v1] of m1.entries()) {
    // -- 1. 类型是相同的
    if (!equals(getSingleType(v1), getSingleType(m2.get(k1)))) {
      return false;
    }
    const v2 = m2.get(k1);

    if (!commonJudge(v1, v2, comparedCache)) {
      return false;
    }
  }

  return true;
}

/**
 * @author lihh
 * @description 内部表两个值的方法
 * @param v1 value值1
 * @param v2 value值2
 * @param comparedCache 比较完 cache
 */
function innerCompareBothObject(
  v1: unknown,
  v2: unknown,
  comparedCache: Array<undefined>
) {
  return commonJudge(v1, v2, comparedCache);
}
